Skip to content

feat: deadline helpers (#852), multi-invoice payment aggregator (#853), options-only tenant pool (#846), property tests (#845) - #898

Merged
Kingsman-99 merged 4 commits into
Stellar-split:mainfrom
willi-d7:feat/852-853-deadline-helpers-payment-aggregator
Sep 28, 2026
Merged

Kingsman-99 merged 4 commits into
Stellar-split:mainfrom
willi-d7:feat/852-853-deadline-helpers-payment-aggregator

Conversation

@willi-d7

Copy link
Copy Markdown

Summary

Closes four open issues in one PR:

  • Add deadline engine — helpers for computing and validating invoice deadlines #852 — deadline engine helpers. New src/deadline.ts module with bigint-based helpers that match the on-chain u64 representation: deadlineFromDays (rounded up so any positive duration is strictly in the future), deadlineFromDate, isDeadlineValid (1-hour minimum lead time), timeUntilDeadline (returns { days, hours, minutes, seconds, expired }, all zeros once expired) and formatDeadline (locale-aware, rendered in UTC for deterministic output). Exported from the package root and documented in the README. The legacy number-returning deadlineFromDays on the @stellar-split/sdk/utils entry point is left untouched and its README row points to the new helper.
  • Implement payment aggregator — compute optimal payment split across multiple invoices #853 — payment aggregator. New src/paymentAllocation.ts module implementing aggregatePayments(budget, invoiceIds, strategy, options?) with all three strategies:
    • equal — even split, surplus redistributed;
    • proportional — weighted by remaining amount, or by fraction of target still unfunded when targets are supplied (so invoices furthest from their target receive the most);
    • custom — caller weights that are validated to sum to 100 (length, non-negativity and finiteness are also validated).
      Every allocation is capped at the invoice's remaining amount so overpayment is impossible, and rounding dust is redistributed deterministically with the largest-remainder/water-filling algorithm, guaranteeing the allocations always sum to min(budget, total remaining). Empty invoice lists return []; duplicate IDs, negative budgets, unknown strategies and invalid weights throw ValidationError. Remaining amounts are resolved from an explicit map/record, a fetchRemaining function, an invoiceSource (any object with getInvoice(id)), or a fetcher registered via registerInvoiceRemainingFetcher — with remainingForInvoice and createInvoiceRemainingFetcher helpers for the on-chain recipients - funded derivation.
  • Implement multi-tenant client pool with TTL eviction and health checks #846 — multi-tenant client pool. The pool already implemented LRU eviction, TTL expiry, background health checks and stats(), but it could only be created as new MultiTenantClient(factory, options) while the documented API is new MultiTenantClient({ maxClients, ttlMs, healthCheckIntervalMs }). The constructor now accepts either form: factory + options (unchanged, backwards compatible) or options alone, in which case getClient(tenantId, config) supplies the tenant config. A missing factory and config now raises a descriptive ValidationError instead of an opaque TypeError.
  • Add property-based testing suite using fast-check #845 — property-based testing. fast-check was already a dev dependency with five property files; this PR extends the suite to the new modules and wires the suites into npm test so the criteria are actually exercised by CI: new test/property-payment-allocation.test.ts (7 properties, 500 examples each: budget never exceeded, no overpayment per invoice, min(budget, total remaining) fully distributed, input order preserved, percentages in [0, 100], valid weights accepted, invalid weights rejected) and 4 new 500-example properties in test/property-deadline.test.ts for the bigint helpers (future timestamps for any n > 0, the 1-hour rule, non-negative countdown units, exact duration reconstruction).

Approach

  • New, self-contained modules (src/deadline.ts, src/paymentAllocation.ts) following the existing conventions: JSDoc on every export, ValidationError from src/errors.js for input validation, registered-fetcher pattern (registerInvoiceRemainingFetcher) like registerInvoiceFetcher/registerChannelStateFetcher, and root re-exports added to src/index.ts with the #nnn — section banners used by the rest of the file.
  • bigint arithmetic end-to-end (no floats) for allocations; the water-filling allocator caps shares at each invoice's remaining amount and redistributes the surplus over remaining passes, then resolves sub-stroop rounding with the largest-remainder method.
  • src/multiTenant.ts keeps its existing behaviour and only widens the constructor signature (factory | options) plus the guard in getClient, so all existing call sites and tests keep working.

How it was tested

Command Result
npm test ✅ 7 files, 203 tests, 0 failures (client, retryPolicy, deadline, paymentAllocation, multiTenant, property-deadline, property-payment-allocation)
npx vitest run test/paymentAllocation.test.ts ✅ 36 tests (equal / proportional / custom / overpayment / single invoice / empty list / validation / source resolution)
npx vitest run test/deadline.test.ts ✅ 20 tests (past, same-day, far-future, exactly-1-hour, expiry, en-US vs de-DE locale formatting)
npx vitest run test/multiTenant.test.ts ✅ 29 tests (existing LRU/TTL/health-check/stats suite + options-only constructor, maxClients without a factory, missing-config ValidationError)
npx vitest run test/property-*.test.ts ✅ 500 examples per property
npx tsc --noEmit ✅ no new errors (error count unchanged at 210, all pre-existing on main and unrelated to these files; the four touched/added modules type-check clean under --strict --noUncheckedIndexedAccess)

ESLint is not configured in this repository (npm run lint maps to tsc --noEmit), so that check is covered by the type-check run above.

closes #852
closes #853
closes #846
closes #845

…s and multi-invoice payment aggregator

Stellar-split#852 — deadline helpers:
- `deadlineFromDays(days)` returns a bigint Unix timestamp, rounded up so any
  positive duration is strictly in the future
- `deadlineFromDate(date)` converts a Date to Unix seconds
- `isDeadlineValid(deadline)` enforces the 1-hour minimum lead time
- `timeUntilDeadline(deadline)` returns { days, hours, minutes, seconds, expired }
- `formatDeadline(deadline, locale?)` renders a localized, UTC date string

Stellar-split#853 — payment aggregator:
- `aggregatePayments(budget, invoiceIds, strategy, options?)` allocates a budget
  across invoices using the `equal`, `proportional` or `custom` strategy
- allocations are capped at each invoice's remaining amount (no overpayment)
  and rounding dust is redistributed via the largest-remainder method
- `proportional` weights by remaining amount, or by fraction-of-target when
  `targets` are supplied (i.e. how far each invoice is from its target)
- `custom` weights are validated to sum to 100
- remaining amounts are resolved from a map/record, a `fetchRemaining` fn, an
  `invoiceSource` or a registered fallback fetcher; empty lists return []

Both modules are exported from the package root and documented in the README.
…ruction

The pool already offered LRU eviction, TTL expiry, background health checks and
`stats()`. It could however only be constructed with a tenant->config factory
first, while the documented API is `new MultiTenantClient({ maxClients, ttlMs,
healthCheckIntervalMs })`.

The constructor now accepts either form: a factory plus `PoolOptions` (unchanged
behaviour, fully backwards compatible) or options alone. When no factory is
registered the tenant config must be passed to `getClient(tenantId, config)`;
a missing factory *and* config raises a clear `ValidationError` instead of a
TypeError. Tests cover the options-only form, maxClients enforcement without a
factory, the factory form and the missing-config error.
…new modules

- new `test/property-payment-allocation.test.ts`: 500 examples per property
  proving allocations never exceed the budget or an invoice's remaining amount,
  always distribute min(budget, total remaining), preserve input order and keep
  percentages in range; custom weights summing to 100 are accepted and invalid
  weights rejected
- `test/property-deadline.test.ts`: 500-example properties for the bigint
  deadline helpers (future timestamps for any n > 0, the 1-hour validity rule,
  non-negative countdown units and exact duration reconstruction)
- `npm test` now also runs the deadline, payment allocation and multi-tenant
  suites so these acceptance criteria are exercised by CI
@drips-wave

drips-wave Bot commented Sep 25, 2026

Copy link
Copy Markdown

@willi-d7 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Kingsman-99
Kingsman-99 merged commit 7d68b2f into Stellar-split:main Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants